﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;

namespace POP.Mime.Decode
{
	public static class EncodingFinder
	{
		public delegate Encoding FallbackDecoderDelegate(string characterSet);

		// Last resort decoder.
		public static FallbackDecoderDelegate FallbackDecoder { private get; set; }

		// Mapping from charactersets to encodings.
		private static Dictionary<string, Encoding> EncodingMap { get; set; }

		// Initialize the EncodingFinder
		static EncodingFinder()
		{
			Reset();
		}

		// Used to reset this static class to facilite isolated unit testing.
		internal static void Reset()
		{
			EncodingMap = new Dictionary<string, Encoding>();
			FallbackDecoder = null;

			// Some emails incorrectly specify the encoding as utf8, but it should have been utf-8.
			AddMapping("utf8", Encoding.UTF8);
		}

		// Parses a character set into an encoding.
		internal static Encoding FindEncoding(string characterSet)
		{
			if (characterSet == null)
				throw new ArgumentNullException("characterSet");

			string charSetUpper = characterSet.ToUpperInvariant();

			// Check if the characterSet is explicitly mapped to an encoding
			if (EncodingMap.ContainsKey(charSetUpper))
				return EncodingMap[charSetUpper];

			// Try to generally find the encoding
			try
			{
				if (charSetUpper.Contains("WINDOWS") || charSetUpper.Contains("CP"))
				{
					// It seems the characterSet contains an codepage value, which we should use to parse the encoding
					charSetUpper = charSetUpper.Replace("CP", ""); // Remove cp
					charSetUpper = charSetUpper.Replace("WINDOWS", ""); // Remove windows
					charSetUpper = charSetUpper.Replace("-", ""); // Remove - which could be used as cp-1554

					// Now we hope the only thing left in the characterSet is numbers.
					int codepageNumber = int.Parse(charSetUpper, CultureInfo.InvariantCulture);

					return Encoding.GetEncoding(codepageNumber);
				}

				// It seems there is no codepage value in the characterSet. It must be a named encoding
				return Encoding.GetEncoding(characterSet);
			}
			catch (ArgumentException)
			{
				// The encoding could not be found generally. 
				// Try to use the FallbackDecoder if it is defined.

				// Check if it is defined
				if (FallbackDecoder == null)
					throw; // It was not defined - throw catched exception

				// Use the FallbackDecoder
				Encoding fallbackDecoderResult = FallbackDecoder(characterSet);

				// Check if the FallbackDecoder had a solution
				if (fallbackDecoderResult != null)
					return fallbackDecoderResult;

				// If no solution was found, throw catched exception
				throw;
			}
		}

		public static void AddMapping(string characterSet, Encoding encoding)
		{
			if (characterSet == null)
				throw new ArgumentNullException("characterSet");

			if(encoding == null)
				throw new ArgumentNullException("encoding");

			// Add the mapping using uppercase
			EncodingMap.Add(characterSet.ToUpperInvariant(), encoding);
		}
	}
}
